/*

   Derby - Class org.apache.derby.impl.sql.compile.ExpressionClassBuilder

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.dearbaby.impl.sql.compile;

import java.io.Serializable;
import java.lang.reflect.Modifier;

import org.apache.derby.iapi.reference.ClassName;
import org.apache.derby.iapi.reference.StandardException;
import org.apache.derby.iapi.services.compiler.ClassBuilder;
import org.apache.derby.iapi.services.compiler.JavaFactory;
import org.apache.derby.iapi.services.compiler.LocalField;
import org.apache.derby.iapi.services.compiler.MethodBuilder;
import org.apache.derby.iapi.services.io.FormatableArrayHolder; 
import org.apache.derby.iapi.sql.compile.CompilerContext;
import org.apache.derby.iapi.sql.compile.ExpressionClassBuilderInterface;
import org.apache.derby.iapi.sql.compile.TypeCompiler; 
import org.apache.derby.iapi.types.TypeId;
import org.apache.derby.iapi.util.ByteArray; 
import org.apache.derby.shared.common.sanity.SanityManager;

/**
 * ExpressionClassBuilder provides an interface to satisfy generation's common
 * tasks in building classes that involve expressions. This is the common
 * superclass of ActivationClassBuilder and FilterClassBuilder. See the
 * documentation on ActivationClassBuilder.
 *
 */
abstract class ExpressionClassBuilder implements
		ExpressionClassBuilderInterface {
	// /////////////////////////////////////////////////////////////////////
	//
	// CONSTANTS
	//
	// /////////////////////////////////////////////////////////////////////

	static final protected String currentDatetimeFieldName = "cdt";

	// /////////////////////////////////////////////////////////////////////
	//
	// STATE
	//
	// /////////////////////////////////////////////////////////////////////

	protected ClassBuilder cb; 
	protected int nextExprNum;
	protected int nextNonFastExpr;
	protected int nextFieldNum;
	protected MethodBuilder constructor;
	CompilerContext myCompCtx;
	MethodBuilder executeMethod; // to find it fast

	protected LocalField cdtField;

	// protected final JavaFactory javaFac;

	private String currentRowScanResultSetName;

	// /////////////////////////////////////////////////////////////////////
	//
	// CONSTRUCTORS
	//
	// /////////////////////////////////////////////////////////////////////

	/**
	 * By the time this is done, it has constructed the following class:
	 * 
	 * <pre>
	 *    final public class #className extends #superClass {
	 *      public #className() { super(); }
	 *    }
	 * </pre>
	 *
	 * @exception StandardException
	 *                thrown on failure
	 */
	ExpressionClassBuilder(String superClass, String className,
			CompilerContext cc) throws StandardException {
		int modifiers = Modifier.PUBLIC | Modifier.FINAL;

		myCompCtx = cc;
		 
		if (className == null) {
			className = myCompCtx.getUniqueClassName();
		}
 

		beginConstructor();
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// ABSTRACT METHODS TO BE IMPLEMENTED BY CHILDREN
	//
	// /////////////////////////////////////////////////////////////////////

	/**
	 * Get the name of the package that the generated class will live in.
	 *
	 * @return name of package that the generated class will live in.
	 */
	abstract String getPackageName();

	/**
	 * Get the number of ExecRows that must be allocated
	 *
	 * @return number of ExecRows that must be allocated
	 *
	 * @exception StandardException
	 *                thrown on failure
	 */
	abstract int getRowCount() throws StandardException;

	/**
	 * Sets the number of subqueries under this expression
	 *
	 *
	 * @exception StandardException
	 *                thrown on failure
	 */
	abstract void setNumSubqueries() throws StandardException;

	// /////////////////////////////////////////////////////////////////////
	//
	// ACCESSORS
	//
	// /////////////////////////////////////////////////////////////////////

	/**
	 * Return the base class of the activation's hierarchy (the subclass of
	 * Object).
	 * 
	 * This class is expected to hold methods used by all compilation code, such
	 * as datatype compilation code, e.g. getDataValueFactory.
	 */
	abstract String getBaseClassName();

	MethodBuilder getConstructor() {
		return constructor;
	}

	ClassBuilder getClassBuilder() {
		return cb;
	}

	/**
	 * Get a method builder for adding code to the execute() method. The method
	 * builder does not actually build a method called execute. Instead, it
	 * creates a method that overrides the reinit() method, which is called from
	 * execute() on every execution in order to reinitialize the data
	 * structures.
	 */
	MethodBuilder getExecuteMethod() {
		if (executeMethod == null) {
			executeMethod = cb.newMethodBuilder(Modifier.PROTECTED, "void",
					"reinit");
			executeMethod.addThrownException(ClassName.StandardException);
		}
		return executeMethod;
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// CONSTRUCTOR MANAGEMENT
	//
	// /////////////////////////////////////////////////////////////////////

	private void beginConstructor() {
		// create a constructor that just calls super.
		MethodBuilder realConstructor = cb
				.newConstructorBuilder(Modifier.PUBLIC);
		realConstructor.callSuper();
		realConstructor.methodReturn();
		realConstructor.complete();

		constructor = cb.newMethodBuilder(Modifier.PUBLIC, "void",
				"postConstructor");
		constructor.addThrownException(ClassName.StandardException);
	}

	/**
	 * Finish the constructor by newing the array of Rows and putting a return
	 * at the end of it.
	 *
	 * @exception StandardException
	 *                thrown on failure
	 */

	void finishConstructor() throws StandardException {
		int numResultSets;

		/* Set the number of subqueries */
		setNumSubqueries();

		numResultSets = getRowCount();

		/*
		 * Generate the new of ExecRow[numResultSets] when there are ResultSets
		 * which return Rows.
		 */
		if (numResultSets >= 1) {
			addNewArrayOfRows(numResultSets);
		}

		/*
		 * Generated code is: return;
		 */
		constructor.methodReturn();
		constructor.complete();
	}

	/**
	 * Generate the assignment for row = new ExecRow[numResultSets]
	 *
	 * @param numResultSets
	 *            The size of the array.
	 */
	private void addNewArrayOfRows(int numResultSets) {
		/*
		 * Generated code is: row = new ExecRow[numResultSets];
		 */

		constructor.pushThis();
		constructor.pushNewArray(ClassName.ExecRow, numResultSets);
		constructor.putField(ClassName.BaseActivation, "row", ClassName.ExecRow
				+ "[]");
		constructor.endStatement();
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// ADD FIELDS TO GENERATED CLASS
	//
	// /////////////////////////////////////////////////////////////////////

	/**
	 * Add a field declaration to the generated class
	 * 
	 * @param modifiers
	 *            The | of the modifier values such as public, static, etc.
	 * @param type
	 *            The type of the field in java language.
	 * @param name
	 *            The name of the field.
	 *
	 * @return None.
	 */
	LocalField newFieldDeclaration(int modifiers, String type, String name) {
		return cb.addField(type, name, modifiers);
	}

	/**
	 * Add an arbitrarily named field to the generated class.
	 *
	 * This is used to generate fields where the caller doesn't care what the
	 * field is named. It is especially useful for generating arbitrary numbers
	 * of fields, where the caller doesn't know in advance how many fields will
	 * be used. For example, it is used for generating fields to hold
	 * intermediate values from expressions.
	 *
	 * @param modifiers
	 *            The | of the modifier values such as public, static, etc.
	 * @param type
	 *            The type of the field in java language.
	 *
	 * @return The name of the new field
	 */

	LocalField newFieldDeclaration(int modifiers, String type) {
		return cb.addField(type, newFieldName(), modifiers);
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// ADD FUNCTIONS TO GENERATED CLASS
	//
	// /////////////////////////////////////////////////////////////////////

	/**
	 * Activations might have need of internal functions that are not used by
	 * the result sets, but by other activation functions. Thus, we make it
	 * possible for functions to be generated directly as well as through the
	 * newExprFun interface. newExprFun should be used when a static field
	 * pointing to the expression function is needed.
	 * <p>
	 * The generated function will generally have a generated name that can be
	 * viewed through the MethodBuilder interface. This name is generated to
	 * ensure uniqueness from other function names in the activation class. If
	 * you pass in a function name, think carefully about whether it will
	 * collide with other names.
	 *
	 * @param returnType
	 *            the return type of the function
	 * @param modifiers
	 *            the modifiers on the function
	 *
	 * @see #newExprFun
	 */
	MethodBuilder newGeneratedFun(String returnType, int modifiers) {

		return newGeneratedFun(returnType, modifiers, (String[]) null);
	}

	MethodBuilder newGeneratedFun(String returnType, int modifiers,
			String[] params) {

		String exprName = "g".concat(Integer.toString(nextNonFastExpr++));
		return newGeneratedFun(exprName, returnType, modifiers, params);

	}

	private MethodBuilder newGeneratedFun(String exprName, String returnType,
			int modifiers, String[] params) {

		//
		// create a new method supplying the given modifiers and return Type
		// Java: #modifiers #returnType #exprName { }
		//
		MethodBuilder exprMethod;
		if (params == null) {
			exprMethod = cb.newMethodBuilder(modifiers, returnType, exprName);
		} else {
			exprMethod = cb.newMethodBuilder(modifiers, returnType, exprName,
					params);
		}

		//
		// declare it to throw StandardException
		// Java: #modifiers #returnType #exprName throws StandardException { }
		//
		exprMethod.addThrownException(ClassName.StandardException);

		return exprMethod;
	}

	/**
	 * "ExprFun"s are the "expression functions" that are specific to a given
	 * JSQL statement. For example, an ExprFun is generated to evaluate the
	 * where clause of a select statement and return a boolean result.
	 * <p>
	 *
	 * All methods return by this are expected to be called via the
	 * GeneratedMethod interface. Thus the methods are public and return
	 * java.lang.Object.
	 * <p>
	 * Once the exprfun has been created, the caller will need to add statements
	 * to it, minimally a return statement.
	 * <p>
	 * ExprFuns return Object types, since they are invoked through reflection
	 * and thus their return type would get wrapped in an object anyway. For
	 * example: return java.lang.Boolean, not boolean.
	 */
	MethodBuilder newExprFun() {
		// get next generated function
		String exprName = "e".concat(Integer.toString(nextExprNum++));

		return newGeneratedFun(exprName, "java.lang.Object", Modifier.PUBLIC,
				(String[]) null);
	}

	/**
	 * Push an expression that is a GeneratedMethod reference to the passed in
	 * method. aka. a "function pointer".
	 */
	void pushMethodReference(MethodBuilder mb, MethodBuilder exprMethod) {

		mb.pushThis(); // instance
		mb.push(exprMethod.getName()); // arg
 
	}

	/**
	 * Start a user expression. The difference between a normal expression
	 * (returned by newExprFun) and a user expression is that a user expression
	 * catches all exceptions (because we don't want random exceptions thrown
	 * from user methods to propagate to the rest of the system.
	 *
	 * @return A new MethodBuilder
	 */
	MethodBuilder newUserExprFun() {

		MethodBuilder mb = newExprFun();
		mb.addThrownException("java.lang.Exception");
		return mb;
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// CURRENT DATE/TIME SUPPORT
	//
	// /////////////////////////////////////////////////////////////////////

	/**
	 * This utility method returns an expression for CURRENT_DATE. Get the
	 * expression this way, because the activation needs to generate support
	 * information for CURRENT_DATE, that would otherwise be painful to create
	 * manually.
	 */
	void getCurrentDateExpression(MethodBuilder mb) {
		// do any needed setup
		LocalField lf = getCurrentSetup();

		// generated Java:
		// this.cdt.getCurrentDate();
		mb.getField(lf);
	 
	}

	/**
	 * This utility method returns an expression for CURRENT_TIME. Get the
	 * expression this way, because the activation needs to generate support
	 * information for CURRENT_TIME, that would otherwise be painful to create
	 * manually.
	 */
	void getCurrentTimeExpression(MethodBuilder mb) {
		// do any needed setup
		LocalField lf = getCurrentSetup();

		// generated Java:
		// this.cdt.getCurrentTime();
		mb.getField(lf);
	 
	}

	/**
	 * This utility method generates an expression for CURRENT_TIMESTAMP. Get
	 * the expression this way, because the activation needs to generate support
	 * information for CURRENT_TIMESTAMP, that would otherwise be painful to
	 * create manually.
	 */
	void getCurrentTimestampExpression(MethodBuilder mb) {
		// do any needed setup
		LocalField lf = getCurrentSetup();

		// generated Java:
		// this.cdt.getCurrentTimestamp();
		mb.getField(lf);
	 
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// COLUMN ORDERING
	//
	// /////////////////////////////////////////////////////////////////////
  
	int addItem(Object o) {
		if (SanityManager.DEBUG) {
			if ((o != null) && !(o instanceof Serializable)) {
				SanityManager.THROWASSERT("o (" + o.getClass().getName()
						+ ") expected to be instanceof java.io.Serializable");
			}
		}
		return myCompCtx.addSavedObject(o);
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// Caching resuable Expressions
	//
	// /////////////////////////////////////////////////////////////////////

	/**
	 * Get/reuse the Expression for getting the DataValueFactory
	 */
	private Object getDVF;

	void pushDataValueFactory(MethodBuilder mb) {
		// generates:
		// getDataValueFactory()
		//
 
		mb.pushThis();
		mb.callMethod(getDVF);
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// RESULT SET SUPPORT
	//
	// /////////////////////////////////////////////////////////////////////

	/**
	 * This is a utility method to get a common expression --
	 * "BaseActivation.getResultSetFactory()".
	 * <p>
	 * BaseActivation gets the factory from the context and caches it for faster
	 * retrieval.
	 */
	private Object getRSF;

	void pushGetResultSetFactoryExpression(MethodBuilder mb) {
	 
	}

	/**
	 * This is a utility method to get a common expression --
	 * "BaseActivation.getExecutionFactory()". REVISIT: could the same
	 * expression objects be reused within the tree and have the correct java
	 * generated each time?
	 * <p>
	 * BaseActivation gets the factory from the context and caches it for faster
	 * retrieval.
	 */
	private Object getEF;

	void pushGetExecutionFactoryExpression(MethodBuilder mb) {
		 

		// generated Java:
		// this.getExecutionFactory()
		//
		mb.pushThis();
		mb.callMethod(getEF);
	}

	/**
	 * Generate a reference to the row array that all activations use.
	 * 
	 * @param eb
	 *            the expression block
	 *
	 * @return expression
	 */
	// private void pushRowArrayReference(MethodBuilder mb)
	// {
	// PUSHCOMPILE - cache
	// mb.pushThis();
	// mb.getField(ClassName.BaseActivation, "row", ClassName.ExecRow + "[]");
	// }

	/**
	 * Generate a reference to a colunm in a result set.
	 * 
	 * @param rsNumber
	 *            the result set number
	 * @param colId
	 *            the column number
	 */
	void pushColumnReference(MethodBuilder mb, int rsNumber, int colId) {
		mb.pushThis();
		mb.push(rsNumber);
		mb.push(colId);
	 

		// System.out.println("pushColumnReference ");
		// pushRowArrayReference(mb);
		// mb.getArrayElement(rsNumber); // instance for getColumn
		// mb.push(colId); // first arg
		// mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.Row, "getColumn",
		// ClassName.DataValueDescriptor, 1);
	}

	/**
	 * Generate a reference to the parameter value set that all activations use.
	 * 
	 */
	void pushPVSReference(MethodBuilder mb) {
		// PUSHCOMPILER-WASCACHED
		mb.pushThis();
		mb.getField(ClassName.BaseActivation, "pvs",
				ClassName.ParameterValueSet);
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// CLASS IMPLEMENTATION
	//
	// /////////////////////////////////////////////////////////////////////

	/*
	 * The first time a current datetime is needed, create the class level
	 * support for it.
	 */
	protected LocalField getCurrentSetup() {
		if (cdtField != null)
			return cdtField;

		// generated Java:
		// 1) the field "cdt" is created:
		// private CurrentDatetime cdt;
		cdtField = newFieldDeclaration(Modifier.PRIVATE,
				ClassName.CurrentDatetime, currentDatetimeFieldName);

		// 2) the constructor gets a statement to init CurrentDatetime:
		// cdt = new CurrentDatetime();

		constructor.pushNewStart(ClassName.CurrentDatetime);
		constructor.pushNewComplete(0);
		constructor.setField(cdtField);

		return cdtField;
	}

	/**
	 * generated the next field name available. these are of the form 'e#',
	 * where # is incremented each time. This shares the name space with the
	 * expression methods as Java allows names and fields to have the same name.
	 * This reduces the number of constant pool entries created for a generated
	 * class file.
	 */
	private String newFieldName() {
		return "e".concat(Integer.toString(nextFieldNum++));
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// DEBUG
	//
	// /////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////
	//
	// DATATYPES
	//
	// /////////////////////////////////////////////////////////////////////
	/**
	 * Get the TypeCompiler associated with the given TypeId
	 *
	 * @param typeId
	 *            The TypeId to get a TypeCompiler for
	 *
	 * @return The corresponding TypeCompiler
	 *
	 */
	protected TypeCompiler getTypeCompiler(TypeId typeId) {
		return myCompCtx.getTypeCompilerFactory().getTypeCompiler(typeId);
	}

	// /////////////////////////////////////////////////////////////////////
	//
	// GENERATE BYTE CODE
	//
	// /////////////////////////////////////////////////////////////////////
 

	/**
	 * Get a "this" expression declared as an Activation. This is the commonly
	 * used type of the this expression.
	 *
	 */
	void pushThisAsActivation(MethodBuilder mb) {
		// PUSHCOMPILER - WASCACHED
		mb.pushThis();
		mb.upCast(ClassName.Activation);
	}

	/**
	 * Generate a Null data value. Nothing is required on the stack, a SQL null
	 * data value is pushed.
	 */
	void generateNull(MethodBuilder mb, TypeCompiler tc, int collationType) {
		pushDataValueFactory(mb);
		mb.pushNull(tc.interfaceName());
		tc.generateNull(mb, collationType);
	}

	/**
	 * Generate a Null data value. The express value is required on the stack
	 * and will be popped, a SQL null data value is pushed.
	 */
	void generateNullWithExpress(MethodBuilder mb, TypeCompiler tc,
			int collationType) {
		pushDataValueFactory(mb);
		mb.swap(); // need the dvf as the instance
		mb.cast(tc.interfaceName());
		tc.generateNull(mb, collationType);
	}

	/**
	 * Generate a data value. The value is to be set in the SQL data value is
	 * required on the stack and will be popped, a SQL data value is pushed.
	 */
	void generateDataValue(MethodBuilder mb, TypeCompiler tc,
			int collationType, LocalField field) {
		pushDataValueFactory(mb);
		mb.swap(); // need the dvf as the instance
		tc.generateDataValue(mb, collationType, field);
	}

	/**
	 * generates a variable name for the rowscanresultset. This can not be a
	 * fixed name because in cases like cascade delete same activation class
	 * will be dealing more than one RowScanResultSets for dependent tables.
	 */

	String newRowLocationScanResultSetName() {
		currentRowScanResultSetName = newFieldName();
		return currentRowScanResultSetName;
	}

	// return the Name of ResultSet with the RowLocations to be modified
	// (deleted or updated).
	String getRowLocationScanResultSetName() {
		return currentRowScanResultSetName;
	}

}
